using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using GDFlacTool.Common;
using Un4seen.Bass;
using Un4seen.Bass.Misc;
using Un4seen.Bass.AddOn.Tags;

namespace GDFlacTool.Forms
{
	public partial class FormPlayer : Form
	{
		private int _stream;
		private readonly string _fileName = String.Empty;
		private int _tickCounter;
		private float _gainDb;
		private DSPPROC _myDspAddr;
		private SYNCPROC _sync;
		private int _syncer;
		private int _deviceLatencyMs; // device latency in milliseconds
		private int _deviceLatencyBytes; // device latency in bytes
		private readonly Visuals _vis = new Visuals(); // visuals class instance
	    private const int UpdateInterval = 50;
	    private BASSTimer _updateTimer;
	    private readonly Track _track;
        private int _pluginHandle;
	    private readonly string _extension = string.Empty;
	    private bool _paused;

		public FormPlayer(Track track)
		{
			InitializeComponent();

		    _track = track;
		    _fileName = _track.FullPath;
		    _extension = Path.GetExtension(_fileName);
		}

        private int LoadFlacPlugin()
        {
            string path = Path.Combine(Application.StartupPath, "bassflac.dll");

            if (File.Exists(path))
            {
                return Bass.BASS_PluginLoad(path);
            }

            return 0;
        }

        private int LoadShnPlugin()
        {
            string path = Path.Combine(Application.StartupPath, "in_shn.dll");

            if (File.Exists(path))
            {
                //return Bass.BASS_PluginLoad(path);

                //return BassWinamp.BASS_WINAMP_LoadPlugin(path);

                return 0;
            }

            return 0;
        }

		private void FormPlayer_Load(object sender, EventArgs e)
		{
            BassNet.Registration("stha64@telia.com", "2X28183316182322");

			if ( Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_LATENCY, Handle) )
			{
                if (_extension == ".flac")
                {
                    _pluginHandle = LoadFlacPlugin();
                }

                if(_extension == ".shn")
                {
                    _pluginHandle = LoadShnPlugin();
                }

				var info = new BASS_INFO();
				Bass.BASS_GetInfo( info );
				//Console.WriteLine( info.ToString() );

			    txtInfo.Text += info.ToString();

				_deviceLatencyMs = info.latency;

                GetWaveForm();
			}
			else
			{
                string message = string.Format("There was an error init Bass Audio Library!\n\n{0}", Bass.BASS_ErrorGetCode());

                MessageBox.Show(this, message, @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}

			_updateTimer = new BASSTimer(UpdateInterval);
			_updateTimer.Tick += (timerUpdate_Tick);

			_sync = EndPosition;
		}

		private void FormPlayer_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			_updateTimer.Tick -= (timerUpdate_Tick);
			// close bass
			Bass.BASS_Stop();
			Bass.BASS_Free();
            Bass.BASS_PluginFree(_pluginHandle);
		}

		private void btnPlay_Click(object sender, EventArgs e)
		{
			_updateTimer.Stop();
			
            Bass.BASS_StreamFree(_stream);

			if (_fileName != String.Empty)
			{
				_stream = Bass.BASS_StreamCreateFile(_fileName, 0, 0, BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_PRESCAN);

				if (_stream != 0)
				{
					// used in RMS
					_30Mslength = (int)Bass.BASS_ChannelSeconds2Bytes(_stream, 0.03); // 30ms window
					// latency from milliseconds to bytes
					_deviceLatencyBytes = (int)Bass.BASS_ChannelSeconds2Bytes(_stream, _deviceLatencyMs/1000.0);

					// set a DSP user callback method
					_myDspAddr = MyDspGain;
                    Bass.BASS_ChannelSetDSP(_stream, _myDspAddr, IntPtr.Zero, 2);

					if (_wf2 != null && _wf2.IsRendered)
					{
						// make sure playback and wave form are in sync, since
						// we rended with 16-bit but play here with 32-bit
						_wf2.SyncPlayback(_stream);
						
						long cuein  = _wf2.GetMarker( "CUE" );
						long cueout = _wf2.GetMarker( "END" );

						//int cueinFrame  = _wf2.Position2Frames(cuein);
						//int cueoutFrame = _wf2.Position2Frames(cueout);
						//Console.WriteLine( @"CueIn at {0}sec.; CueOut at {1}sec.", _wf2.Frame2Seconds(cueinFrame), _wf2.Frame2Seconds(cueoutFrame) );

						if (cuein >= 0)
						{
							Bass.BASS_ChannelSetPosition(_stream, cuein);
						}
						if (cueout >= 0)
						{
							Bass.BASS_ChannelRemoveSync(_stream, _syncer);
                            _syncer = Bass.BASS_ChannelSetSync(_stream, BASSSync.BASS_SYNC_POS, cueout, _sync, IntPtr.Zero);
						}
					}
				}

				if (_stream != 0 && Bass.BASS_ChannelPlay(_stream, false) )
				{
					txtInfo.Text = string.Empty;
					_updateTimer.Start();

					var info = new BASS_CHANNELINFO();
					Bass.BASS_ChannelGetInfo(_stream, info);
					txtInfo.Text += @"Audio file information: " + info + Environment.NewLine;
					
					var tagInfo = new TAG_INFO(_fileName);
					if ( BassTags.BASS_TAG_GetFromFile( _stream, tagInfo) )
					{
						lblAlbumInfo.Text = string.Format("{0}", tagInfo.album);
						lblArtistInfo.Text = string.Format("{0}", tagInfo.artist);
						lblTitleInfo.Text = string.Format("{0}", tagInfo.title);
					    lblAlbumArtistInfo.Text = string.Format("{0}", tagInfo.albumartist);
						lblCommentInfo.Text = string.Format("{0}", tagInfo.comment);
						lblGenreInfo.Text = string.Format("{0}", tagInfo.genre);
						lblYearInfo.Text = string.Format("{0}", tagInfo.year);
						lblTrackInfo.Text = string.Format("{0}", tagInfo.track);
					    lblDiscInfo.Text = string.Format("{0}", tagInfo.disc);
					    lblBitrateInfo.Text = string.Format("{0}", tagInfo.bitrate);
						picTagImage.Image = tagInfo.PictureGetImage(0);

						lblPicture.Text = tagInfo.PictureGetDescription(0);
                        if (lblPicture.Text == String.Empty)
                        {
                            lblPicture.Text = tagInfo.PictureGetType(0);
                        }
					}
					else
					{
					    lblAlbumInfo.Text = string.Empty;
					    lblArtistInfo.Text = string.Empty;
					    lblTitleInfo.Text = string.Empty;
					    lblAlbumArtistInfo.Text = string.Empty;
					    lblCommentInfo.Text = string.Empty;
                        lblGenreInfo.Text = string.Empty;
                        lblYearInfo.Text = string.Empty;
                        lblTrackInfo.Text = string.Empty;
                        lblDiscInfo.Text = string.Empty;
					    lblBitrateInfo.Text = string.Empty;
					}
					btnStop.Enabled = true;
					btnPlay.Enabled = false;
				    btnPause.Enabled = true;
				    _paused = false;
				}
				else
				{
                    string message = string.Format("There was an error playing track!\n\n{0}", Bass.BASS_ErrorGetCode());

                    MessageBox.Show(this, message, @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
			}
		}

        private void btnPause_Click(object sender, EventArgs e)
        {
            if(_paused)
            {
                Bass.BASS_ChannelPlay(_stream, false);
                _paused = false;
            }
            else
            {
                Bass.BASS_ChannelPause(_stream);
                _paused = true;
            }
            
        }

		private void btnStop_Click(object sender, EventArgs e)
		{
			_updateTimer.Stop();

			// kills rendering, if still in progress, e.g. if a large file was selected
			if (_wf2 != null && _wf2.IsRenderingInProgress)
				_wf2.RenderStop();
			
			Bass.BASS_StreamFree(_stream);
			_stream = 0;
			btnStop.Enabled = false;
			btnPlay.Enabled = true;
		    btnPause.Enabled = false;
		    _paused = false;
		}

        private void EndPosition(int handle, int channel, int data, IntPtr user)
		{
			Bass.BASS_ChannelStop(channel);
		}

		private void timerUpdate_Tick(object sender, EventArgs e)
		{
			// here we gather info about the stream, when it is playing...
            if (Bass.BASS_ChannelIsActive(_stream) == BASSActive.BASS_ACTIVE_PLAYING || Bass.BASS_ChannelIsActive(_stream) == BASSActive.BASS_ACTIVE_PAUSED)
			{
				// the stream is still playing...
			}
			else
			{
				// the stream is NOT playing anymore...
				_updateTimer.Stop();

			    vuMeterLeft.Level = 0;
			    vuMeterRight.Level = 0;
				lblTime.Text = @"Stopped";
				DrawWavePosition(-1, -1);
				picSpectrum.Image = null;
				btnStop.Enabled = false;
				btnPlay.Enabled = true;
			    btnPause.Enabled = false;
			    _paused = false;
				return;
			}

			// from here on, the stream is for sure playing...
			_tickCounter++;
			long pos = Bass.BASS_ChannelGetPosition(_stream); // position in bytes
			long len = Bass.BASS_ChannelGetLength(_stream); // length in bytes
            int peakL;
            int peakR;
            Rms(_stream, out peakL, out peakR);

            double dBlevelL = Math.Round(Utils.LevelToDB(peakL, 65535),1);
            double dBlevelR = Math.Round(Utils.LevelToDB(peakR, 65535),1);
			
            if (_tickCounter == 5)
			{
				// display the position every 250ms (since timer is 50ms)
				_tickCounter = 0;
                double totaltime = Bass.BASS_ChannelBytes2Seconds(_stream, len); // the total time length
                double elapsedtime = Bass.BASS_ChannelBytes2Seconds(_stream, pos); // the elapsed time length
                double remainingtime = totaltime - elapsedtime;
				lblTime.Text = String.Format( "Elapsed: {0:#0.00} - Total: {1:#0.00} - Remain: {2:#0.00}", Utils.FixTimespan(elapsedtime,"MMSS"), Utils.FixTimespan(totaltime,"MMSS"), Utils.FixTimespan(remainingtime,"MMSS"));
                Text = String.Format("GD Flac Tool Player - CPU: {0:0.00}%  Left: {1:0#.0}dB  Right: {2:0#.0}dB", Bass.BASS_GetCPU(), dBlevelL, dBlevelR);
			}

		    vuMeterLeft.Level = peakL;
		    vuMeterRight.Level = peakR;

			DrawWavePosition(pos, len);

			DrawSpectrum();
		}
		
		#region VU (peak) level meter.

		// this method is a simple demo!
		// See the other implementations (RMS_2, RMS_3, RMS_4) for other examples
		// As you can see, there are many ways to interact with unmanaged code!

		private int _30Mslength;
		private float[] _rmsData;

		private void Rms(int channel, out int peakL, out int peakR)
		{
			float maxL = 0f;
			float maxR = 0f;
			int length = _30Mslength; // 30ms window already set at buttonPlay_Click
			int l4 = length/4; // the number of 32-bit floats required (since length is in bytes!)
			
			// increase our data buffer as needed
			if (_rmsData == null || _rmsData.Length < l4)
			{
			    _rmsData = new float[l4];
			}
				

			// Note: this is a special mechanism to deal with variable length c-arrays.
			// In fact we just pass the address (reference) to the first array element to the call.
			// However the .Net marshal operation will copy N array elements (so actually fill our float[]).
			// N is determined by the size of our managed array, in this case N=l4
			length = Bass.BASS_ChannelGetData(channel, _rmsData, length);

			l4 = length/4; // the number of 32-bit floats received

			for (int a=0; a < l4; a++)
			{
                float absLevel = Math.Abs(_rmsData[a]);
				// decide on L/R channel
				if (a % 2 == 0)
				{
					// L channel
                    if (absLevel > maxL)
                        maxL = absLevel;
				}
				else
				{
					// R channel
                    if (absLevel > maxR)
                        maxR = absLevel;
				}
			}

			// limit the maximum peak levels to +6bB = 0xFFFF = 65535
			// the peak levels will be int values, where 32767 = 0dB!
			// and a float value of 1.0 also represents 0db.
			peakL = (int)Math.Round(32767f * maxL) & 0xFFFF;
			peakR = (int)Math.Round(32767f * maxR) & 0xFFFF;
		}

		#endregion

		#region DSP (gain) routines.

		// this will be our local buffer
		// we use it outside of MyDSPGain for better performance and to reduce
		// the need to alloc it everytime MyDSPGain is called!
		private float[] _data;

		// this local member keeps the amplification level as a float value
		private float _gainAmplification = 1;

		// the DSP callback - safe!
        private void MyDspGain(int handle, int channel, IntPtr buffer, int length, IntPtr user)
		{
			if (_gainAmplification == 1f || length == 0 || buffer == IntPtr.Zero)
				return;

			// length is in bytes, so the number of floats to process is:
			// length / 4 : byte = 8-bit, float = 32-bit
			int l4 = length/4;

			// increase the data buffer as needed
			if (_data == null || _data.Length < l4)
				_data = new float[l4];

			// copy from unmanaged to managed memory
			Marshal.Copy(buffer, _data, 0, l4);

			// apply the gain, assumeing using 32-bit floats (no clipping here ;-)
			for (int a=0; a<l4; a++)
				_data[a] = _data[a] * _gainAmplification;

			// copy back from managed to unmanaged memory
			Marshal.Copy(_data, 0, buffer, l4);
		}

		private void buttonSetGain_Click(object sender, EventArgs e)
		{
			try
			{
				_gainDb = float.Parse(textBoxGainDBValue.Text);
				// convert the _gainDB value to a float
				_gainAmplification = (float)Math.Pow(10d, _gainDb / 20d);
			}
			catch 
			{
				_gainDb = 0f;
				_gainAmplification = 1f;
			}
		}

		#endregion

		#region Wave Form.

		// zoom helper varibales
		private bool _zoomed;
		private int _zoomStart = -1;
		private long _zoomStartBytes = -1;
		private int _zoomEnd = -1;
	    private const float ZoomDistance = 5.0f; // zoom = 5sec.

	    private WaveForm _wf2;

		private void GetWaveForm()
		{
			// unzoom...(display the whole wave form)
			_zoomStart = -1;
			_zoomStartBytes = -1;
			_zoomEnd = -1;
			_zoomed = false;
			// render a wave form
			_wf2 = new WaveForm(_fileName, MyWaveFormCallback, this);
			_wf2.FrameResolution = 0.01f; // 10ms are nice
			_wf2.CallbackFrequency = 2000; // every 30 seconds rendered (3000*10ms=30sec)
			_wf2.ColorBackground = Color.WhiteSmoke;
			_wf2.ColorLeft = Color.PaleGreen;
			_wf2.ColorLeftEnvelope = Color.Gray;
			_wf2.ColorRight = Color.PaleGreen;
			_wf2.ColorRightEnvelope = Color.DimGray;
			_wf2.ColorMarker = Color.DarkBlue;
			_wf2.DrawWaveForm = WaveForm.WAVEFORMDRAWTYPE.Stereo;
			_wf2.DrawMarker = WaveForm.MARKERDRAWTYPE.Line | WaveForm.MARKERDRAWTYPE.Name | WaveForm.MARKERDRAWTYPE.NamePositionAlternate;
			_wf2.MarkerLength = 0.75f;
			// our playing stream will be in 32-bit float!
			// but here we render with 16-bit (default) - just to demo the WF2.SyncPlayback method
			_wf2.RenderStart( true, BASSFlag.BASS_DEFAULT );
		}

		private void MyWaveFormCallback(int framesDone, int framesTotal, TimeSpan elapsedTime, bool finished)
		{
			if (finished)
			{
				Console.WriteLine( @"Finished rendering in {0}sec.", elapsedTime);
				Console.WriteLine( @"FramesRendered={0} of {1}", _wf2.FramesRendered, _wf2.FramesToRender);
				// eg.g use this to save the rendered wave form...
				//WF.WaveFormSaveToFile( @"C:\test.wf" );

				// auto detect silence at beginning and end
				long cuein  = 0;
				long cueout = 0;
				_wf2.GetCuePoints(ref cuein, ref cueout, -25.0, -42.0, -1, -1);
				_wf2.AddMarker( "CUE", cuein );
				_wf2.AddMarker( "END", cueout );
			}
			// will be called during rendering...
			DrawWave();
		}

		private void pictureBox1_Resize(object sender, EventArgs e)
		{
			DrawWave();
		}

		private void DrawWave()
		{
			if (_wf2 != null)
				picWaveForm.BackgroundImage = _wf2.CreateBitmap( picWaveForm.Width, picWaveForm.Height, _zoomStart, _zoomEnd, true);
			else
				picWaveForm.BackgroundImage = null;
		}

		private void DrawWavePosition(long pos, long len)
		{
			// Note: we might take the latency of the device into account here!
			// so we show the position as heard, not played.
			// That's why we called Bass.Bass_Init with the BASS_DEVICE_LATENCY flag
			// and then used the BASS_INFO structure to get the latency of the device

			if (len == 0 || pos < 0)
			{
				picWaveForm.Image = null;
				return;
			}

			Bitmap bitmap;
			Graphics g = null;
			Pen p = null;

		    try
			{
			    double bpp;
			    if (_zoomed)
				{
					// total length doesn't have to be _zoomDistance sec. here
					len = _wf2.Frame2Bytes(_zoomEnd) - _zoomStartBytes;

					const int scrollOffset = 10; // 10*20ms = 200ms.
					// if we scroll out the window...(scrollOffset*20ms before the zoom window ends)
					if ( pos > (_zoomStartBytes + len - scrollOffset*_wf2.Wave.bpf) )
					{
						// we 'scroll' our zoom with a little offset
						_zoomStart = _wf2.Position2Frames(pos - scrollOffset*_wf2.Wave.bpf);
						_zoomStartBytes = _wf2.Frame2Bytes(_zoomStart);
						_zoomEnd = _zoomStart + _wf2.Position2Frames( ZoomDistance ) - 1;
						if (_zoomEnd >= _wf2.Wave.data.Length)
						{
							// beyond the end, so we zoom from end - _zoomDistance.
							_zoomEnd = _wf2.Wave.data.Length-1;
							_zoomStart = _zoomEnd - _wf2.Position2Frames( ZoomDistance ) + 1;
							if (_zoomStart < 0)
								_zoomStart = 0;
							_zoomStartBytes = _wf2.Frame2Bytes(_zoomStart);
							// total length doesn't have to be _zoomDistance sec. here
							len = _wf2.Frame2Bytes(_zoomEnd) - _zoomStartBytes;
						}
						// get the new wave image for the new zoom window
						DrawWave();
					}
					// zoomed: starts with _zoomStartBytes and is _zoomDistance long
					pos -= _zoomStartBytes; // offset of the zoomed window
					
					bpp = len/(double)picWaveForm.Width;  // bytes per pixel
				}
				else
				{
					// not zoomed: width = length of stream
					bpp = len/(double)picWaveForm.Width;  // bytes per pixel
				}

				// we take the device latency into account
				// Not really needed, but if you have a real slow device, you might need the next line
				// so the BASS_ChannelGetPosition might return a position ahead of what we hear
				pos -= _deviceLatencyBytes;

				p = new Pen(Color.Red);
				bitmap = new Bitmap(picWaveForm.Width, picWaveForm.Height);
				g = Graphics.FromImage(bitmap);
				g.Clear( Color.Black );
				var x = (int)Math.Round(pos/bpp);  // position (x) where to draw the line
				g.DrawLine( p, x, 0, x,  picWaveForm.Height-1);
				bitmap.MakeTransparent( Color.Black );
			}
			catch 
			{ 
				bitmap = null; 
			}
			finally
			{
				// clean up graphics resources
				if (p != null)
					p.Dispose();
				if (g != null)
					g.Dispose();
			}

			picWaveForm.Image = bitmap;
		}

		private void buttonZoom_Click(object sender, EventArgs e)
		{
			if (_wf2 == null)
				return;

			// WF is not null, so the stream must be playing...
			if (_zoomed)
			{
				// unzoom...(display the whole wave form)
				_zoomStart = -1;
				_zoomStartBytes = -1;
				_zoomEnd = -1;
			}
			else
			{
				// zoom...(display only a partial wave form)
				long pos = Bass.BASS_ChannelGetPosition(_stream);
				// calculate the window to display
				_zoomStart = _wf2.Position2Frames(pos);
				_zoomStartBytes = _wf2.Frame2Bytes(_zoomStart);
				_zoomEnd = _zoomStart + _wf2.Position2Frames( ZoomDistance ) - 1;
				if (_zoomEnd >= _wf2.Wave.data.Length)
				{
					// beyond the end, so we zoom from end - _zoomDistance.
					_zoomEnd = _wf2.Wave.data.Length-1;
					_zoomStart = _zoomEnd - _wf2.Position2Frames( ZoomDistance ) + 1;
					_zoomStartBytes = _wf2.Frame2Bytes(_zoomStart);
				}
			}
			_zoomed = !_zoomed;
			// and display this new wave form
			DrawWave();
		}

		private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
		{
			if (_wf2 == null)
				return;

			long pos = _wf2.GetBytePositionFromX( e.X, picWaveForm.Width, _zoomStart, _zoomEnd);
			Bass.BASS_ChannelSetPosition(_stream, pos);
		}

		#endregion

		#region Spectrum.

		private int _specIdx = 2;
		private int _voicePrintIdx;
		private void DrawSpectrum()
		{
			switch (_specIdx)
			{
					// normal spectrum (width = resolution)
				case 0:
					picSpectrum.Image = _vis.CreateSpectrum(_stream, picSpectrum.Width, picSpectrum.Height, Color.Lime, Color.Red, Color.Black, false, false, false);
					break;
					// normal spectrum (full resolution)
				case 1:
					picSpectrum.Image = _vis.CreateSpectrum(_stream, picSpectrum.Width, picSpectrum.Height, Color.SteelBlue, Color.Pink, Color.Black, false, true, true);
					break;
					// line spectrum (width = resolution)
				case 2:
					picSpectrum.Image = _vis.CreateSpectrumLine(_stream, picSpectrum.Width, picSpectrum.Height, Color.Lime, Color.Red, Color.Black, 2, 2, false, false, false);
					break;
					// line spectrum (full resolution)
				case 3:
					picSpectrum.Image = _vis.CreateSpectrumLine(_stream, picSpectrum.Width, picSpectrum.Height, Color.SteelBlue, Color.Pink, Color.Black, 16, 4, false, true, true);
					break;
					// ellipse spectrum (width = resolution)
				case 4:
					picSpectrum.Image = _vis.CreateSpectrumEllipse(_stream, picSpectrum.Width, picSpectrum.Height, Color.Lime, Color.Red, Color.Black, 1, 2, false, false, false);
					break;
					// ellipse spectrum (full resolution)
				case 5:
					picSpectrum.Image = _vis.CreateSpectrumEllipse(_stream, picSpectrum.Width, picSpectrum.Height, Color.SteelBlue, Color.Pink, Color.Black, 2, 4, false, true, true);
					break;
					// dot spectrum (width = resolution)
				case 6:
					picSpectrum.Image = _vis.CreateSpectrumDot(_stream, picSpectrum.Width, picSpectrum.Height, Color.Lime, Color.Red, Color.Black, 1, 0, false, false, false);
					break;
					// dot spectrum (full resolution)
				case 7:
					picSpectrum.Image = _vis.CreateSpectrumDot(_stream, picSpectrum.Width, picSpectrum.Height, Color.SteelBlue, Color.Pink, Color.Black, 2, 1, false, false, true);
					break;
					// peak spectrum (width = resolution)
				case 8:
					picSpectrum.Image = _vis.CreateSpectrumLinePeak(_stream, picSpectrum.Width, picSpectrum.Height, Color.SeaGreen, Color.LightGreen, Color.Orange, Color.Black, 2, 1, 2, 10, false, false, false);
					break;
					// peak spectrum (full resolution)
				case 9:
					picSpectrum.Image = _vis.CreateSpectrumLinePeak(_stream, picSpectrum.Width, picSpectrum.Height, Color.GreenYellow, Color.RoyalBlue, Color.DarkOrange, Color.Black, 23, 5, 3, 5, false, true, true);
					break;
					// wave spectrum (width = resolution)
				case 10:
					picSpectrum.Image = _vis.CreateSpectrumWave(_stream, picSpectrum.Width, picSpectrum.Height, Color.Yellow, Color.Orange, Color.Black, 1, false, false, false);
					break;
					// dancing beans spectrum (width = resolution)
				case 11:
					picSpectrum.Image = _vis.CreateSpectrumBean(_stream, picSpectrum.Width, picSpectrum.Height, Color.Chocolate, Color.DarkGoldenrod, Color.Black, 4, false, false, true);
					break;
					// dancing text spectrum (width = resolution)
				case 12:
					picSpectrum.Image = _vis.CreateSpectrumText(_stream, picSpectrum.Width, picSpectrum.Height, Color.White, Color.Tomato, Color.Black, "GD FLAC TOOL", false, false, true);
					break;
					// frequency detection
				case 13:
					float amp = _vis.DetectFrequency(_stream, 10, 500, true);
					picSpectrum.BackColor = amp > 0.3 ? Color.Red : Color.Black;
					break;
					// 3D voice print
				case 14:
					// we need to draw directly directly on the picture box...
					// normally you would encapsulate this in your own custom control
					Graphics g = Graphics.FromHwnd(picSpectrum.Handle);
					g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
					_vis.CreateSpectrum3DVoicePrint(_stream, g, new Rectangle(0, 0, picSpectrum.Width, picSpectrum.Height), Color.Black, Color.White, _voicePrintIdx, false, false);
					g.Dispose();
					// next call will be at the next pos
					_voicePrintIdx++;
					if (_voicePrintIdx > picSpectrum.Width-1)
						_voicePrintIdx = 0;
					break;
					// WaveForm
				case 15:
					picSpectrum.Image = _vis.CreateWaveForm(_stream, picSpectrum.Width, picSpectrum.Height, Color.Green, Color.Red, Color.Gray, Color.Black, 1, true, false, true);
					break;
			}
		}

		private void pictureBoxSpectrum_MouseDown(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Left)
				_specIdx++;
			else
				_specIdx--;

			if (_specIdx > 15)
				_specIdx = 0;
			if (_specIdx < 0)
				_specIdx = 15;
			lblVis.Text = String.Format( "{0} of 16 (click L/R mouse to change)", _specIdx+1);
			picSpectrum.Image = null;
			_vis.ClearPeaks();
		}

		#endregion Spectrum
	}
}
